Skip to content

Preserve select_related joins in values()/values_list() (#2004)#2224

Open
gaoflow wants to merge 1 commit into
tortoise:developfrom
gaoflow:fix-2004-select-related-values
Open

Preserve select_related joins in values()/values_list() (#2004)#2224
gaoflow wants to merge 1 commit into
tortoise:developfrom
gaoflow:fix-2004-select-related-values

Conversation

@gaoflow

@gaoflow gaoflow commented Jun 20, 2026

Copy link
Copy Markdown

Description

Fixes #2004.

select_related(...) requests a LEFT OUTER JOIN, but .values() and .values_list() silently dropped it. When an annotation, filter or ordering referenced the joined table — e.g. a RawSQL term used for something the ORM can't express directly, like a cast — the generated SQL referenced the related alias with no matching join, so the query crashed:

q = (Book.all().select_related("author")
       .annotate(custom=RawSQL("book__author.age * 2"))
       .order_by("custom"))

q.sql()                              # has the JOIN
q.values_list("id", flat=True).sql()
# SELECT "id" FROM "book" ORDER BY book__author.age * 2 ASC   -- JOIN gone
await q.values_list("id", flat=True)
# OperationalError: no such column: book__author.age

The regular QuerySet path joins correctly (QuerySet._make_query calls _join_select_related); only the value-query path was missing it.

Fix

Thread the requested select_related lookups through to ValuesQuery / ValuesListQuery and register the joins in their _make_query. A small AwaitableQuery._join_select_related_tables() helper adds only the joins (not the related columns, which the projection does not select), so:

  • the related alias resolves and the query runs;
  • .values() / .values_list() output shape is unchanged (no extra columns);
  • queries without select_related are unaffected (no spurious joins).

Tests

Added test_select_related_join_preserved_in_values and ..._in_values_list to tests/test_values.py. Both fail on develop with OperationalError: no such column and pass with the fix. The full tests/test_values.py and tests/test_queryset.py suites pass, and ruff/mypy are clean.

QuerySet.values() and values_list() dropped the LEFT OUTER JOINs that
select_related() had requested. When an annotation, filter or ordering
referenced the related table (e.g. a RawSQL term), the generated SQL
referenced an alias with no matching join and raised "no such column".

Thread the requested select_related lookups through to the value queries
and register the joins (without selecting the related columns, which the
projection does not need) so the alias resolves, matching QuerySet.

Fixes tortoise#2004.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

select_related not preserved in ValuesQuery

1 participant